package com.taobao.diamond.io.watch;

import com.taobao.diamond.io.Path;
import com.taobao.diamond.io.watch.util.PathNode;
import java.io.File;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class WatchKey
{
  private volatile boolean valid;
  private final PathNode root;
  private List<WatchEvent<?>> changedEvents;
  private final Set<WatchEvent.Kind<?>> filterSet = new HashSet();
  private final WatchService watcher;

  public WatchKey(Path path, WatchService watcher, boolean fireCreatedEventOnIndex, WatchEvent.Kind<?>[] events)
  {
    this.valid = true;
    this.watcher = watcher;

    this.root = new PathNode(path, true);
    if (events != null) {
      for (WatchEvent.Kind event : events) {
        this.filterSet.add(event);
      }
    }
    LinkedList changedEvents = new LinkedList();
    index(this.root, fireCreatedEventOnIndex, changedEvents);
    this.changedEvents = changedEvents;
  }

  private void index(PathNode node, boolean fireCreatedEventOnIndex, LinkedList<WatchEvent<?>> changedEvents)
  {
    File file = node.getPath().getFile();
    if (!file.isDirectory()) {
      return;
    }
    File[] subFiles = file.listFiles();
    if (subFiles != null)
      for (File subFile : subFiles) {
        PathNode subNode = new PathNode(new Path(subFile), false);
        if (fireCreatedEventOnIndex) {
          changedEvents.add(new WatchEvent(StandardWatchEventKind.ENTRY_CREATE, 1, subNode.getPath()));
        }
        node.addChild(subNode);
        if (subNode.getPath().isDirectory())
          index(subNode, fireCreatedEventOnIndex, changedEvents);
      }
  }

  public void cancel()
  {
    this.valid = false;
  }

  public String toString()
  {
    return "WatchKey [root=" + this.root + ", valid=" + this.valid + "]";
  }

  public boolean isValid()
  {
    return (this.valid) && (this.root != null);
  }

  public List<WatchEvent<?>> pollEvents()
  {
    if (this.changedEvents != null) {
      List result = this.changedEvents;
      this.changedEvents = null;
      return result;
    }
    return null;
  }

  boolean check()
  {
    if ((this.changedEvents != null) && (this.changedEvents.size() > 0))
      return true;
    if (!this.valid)
      return false;
    List list = new LinkedList();
    if (check(this.root, list)) {
      this.changedEvents = list;
      return true;
    }

    return false;
  }

  private boolean check(PathNode node, List<WatchEvent<?>> changedEvents)
  {
    Path nodePath = node.getPath();
    File nodeNewFile = new File(nodePath.getAbsolutePath());
    if (nodePath != null) {
      if (node.isRoot()) {
        if (!nodeNewFile.exists()) {
          return fireOnRootDeleted(changedEvents, nodeNewFile);
        }
        return checkNodeChildren(node, changedEvents, nodeNewFile);
      }

      return checkNodeChildren(node, changedEvents, nodeNewFile);
    }

    throw new IllegalStateException("PathNode没有path");
  }

  private boolean checkNodeChildren(PathNode node, List<WatchEvent<?>> changedEvents, File nodeNewFile)
  {
    boolean changed = false;
    Iterator it = node.getChildren().iterator();

    Set childNameSet = new HashSet();
    while (it.hasNext()) {
      PathNode child = (PathNode)it.next();
      Path childPath = child.getPath();
      childNameSet.add(childPath.getName());
      File childNewFile = new File(childPath.getAbsolutePath());

      if ((!childNewFile.exists()) && (this.filterSet.contains(StandardWatchEventKind.ENTRY_DELETE))) {
        changed = true;
        changedEvents.add(new WatchEvent(StandardWatchEventKind.ENTRY_DELETE, 1, childPath));
        it.remove();
      }

      if ((childPath.isFile()) &&
        (checkFile(changedEvents, child, childNewFile)) && (!changed)) {
        changed = true;
      }

      if ((childPath.isDirectory()) &&
        (check(child, changedEvents)) && (!changed)) {
        changed = true;
      }

    }

    File[] newChildFiles = nodeNewFile.listFiles();
    if (newChildFiles != null) {
      for (File newChildFile : newChildFiles) {
        if ((!childNameSet.contains(newChildFile.getName())) && (this.filterSet.contains(StandardWatchEventKind.ENTRY_CREATE)))
        {
          changed = true;
          Path newChildPath = new Path(newChildFile);
          changedEvents.add(new WatchEvent(StandardWatchEventKind.ENTRY_CREATE, 1, newChildPath));
          PathNode newSubNode = new PathNode(newChildPath, false);
          node.addChild(newSubNode);

          if (newChildFile.isDirectory())
            checkNodeChildren(newSubNode, changedEvents, newChildFile);
        }
      }
    }
    return changed;
  }

  private boolean checkFile(List<WatchEvent<?>> changedEvents, PathNode child, File childNewFile)
  {
    boolean changed = false;

    if ((childNewFile.lastModified() != child.lastModified()) && (this.filterSet.contains(StandardWatchEventKind.ENTRY_MODIFY)))
    {
      changed = true;
      Path newChildPath = new Path(childNewFile);
      changedEvents.add(new WatchEvent(StandardWatchEventKind.ENTRY_MODIFY, 1, newChildPath));
      child.setPath(newChildPath);
    }
    return changed;
  }

  private boolean fireOnRootDeleted(List<WatchEvent<?>> changedEvents, File nodeNewFile)
  {
    this.valid = false;
    if (this.filterSet.contains(StandardWatchEventKind.ENTRY_DELETE)) {
      changedEvents.add(new WatchEvent(StandardWatchEventKind.ENTRY_DELETE, 1, new Path(nodeNewFile)));
      return true;
    }
    return false;
  }

  public boolean reset()
  {
    if (!this.valid)
      return false;
    if (this.root == null)
      return false;
    return this.watcher.resetKey(this);
  }
}


